Ruby on Rails continues to turn many heads in the software engineering world, but enterprise shops remain relatively unconvinced. Why? We wonder, "How is some framework built on a scripting language going to fit into my enterprise application?!" In the case of Ruby on Rails, the typical argument concerns the lack of support for enterprise services (e.g., distributed transactions, messaging, etc.). To many businesses, a platform without those services simply isn't an option.
Grails aims to address those concerns and legitimize rapid application development (RAD) for the enterprise. Built on Groovy, Grails offers seamless integration with Java. It provides direct access to those enterprise services that your business depends on, and at the same time adds powerful dynamic language constructs to your toolkit.
As one impressive example of its enterprise integration abilities, Grails let's you quickly and easily build a web application backed by your existing EJB3 entity beans. But, it doesn't stop there. Grails gives your entity beans a hearty shot of steroids, but does so completely dynamically, without altering your EJB source code in any way. Grails Object Relational Mapping (GORM) is built on Hibernate 3 (but will eventually offer support for the Java Persistence API) and uses Groovy's Meta Object Protocol (MOP) to add all sorts of handy dynamic methods to your otherwise-static entity beans. And those methods are not only accessible from Grails and Groovy; your Java code can invoke those methods as well! We suddenly have all the enterprise-level capabilities of JEE/EJB3 and the benefits of RAD web application development!
So, let's see what it takes to build a Grails application backed by EJB3 entity beans. In the steps below, we'll create a new Grails application, import entity beans into the application, generate the scaffolding to quickly build a default web interface for the entity beans, and then explore a few of the handy dynamic methods Grails adds to the entity beans.
First we need an EJB3 application to start with. (Well, Grails doesn't require that you have an EJB3 application as your starting point. However, the general assumption of this article is that you're interested in incorporating RAD web development into your EJB3 project.) Let's assume we have some EJB3 entity beans that model the employees (EmployeeBean) in a company and the computers (ComputerBean) assigned to those employees. (Be sure to see the Resources section if you run into any issues along the way. There you'll find the complete source code for the Grails application, a sample JEE application that uses these entity beans, and other useful artifacts.)
The following two tables support our entity beans. (We'll use MySQL 5.0 for this example. You can use this script to create a new database named ejb3example populated with these tables.)
And let's have a quick look at our company's motley lineup of worker bees and their hardware.
How long should it take to build a web user interface that interacts with this data and that integrates with our existing code base? Well, it shouldn't take us long, but we've sure come to accept that this process requires significant effort. With Grails, it doesn't have to be that way.
Step 1 - Install Grails
Since EJB3 is dependent upon JDK 5, you'll want to make sure that you have JDK 5 installed and that your JAVA_HOME environment variable points to your JDK 5 installation.
Follow these quick steps to install Grails on your system. (This article uses Grails 0.2.1 - the current stable release as of this writing.) (Also, if you're using a *nix system, you may need to check out this thread if you encounter installation issues.)
Step 2 - "Hello, Grails!"
- From a command prompt, navigate to the directory where you want to create your Grails application. Then, enter grails create-app. When asked for an application name, enter ejb3_grails.
jMac:~/dev jason$ grails create-app ... create-app: [input] Enter application name: ejb3_grails ... BUILD SUCCESSFUL Total time: 4 seconds
- Just to make sure all is well with our environment, let's start our application. Move into the new directory created for our application, and then enter grails run-app to start the application.
jMac:~/dev jason$ cd ejb3_grails jMac:~/dev/ejb3_grails jason$ grails run-app ... run-app:watch-context:
- The application is now waiting for our requests. Open your browser to http://localhost:8080/ejb3_grails/, and you should see this friendly message welcoming you to Grails.
Step 3 - Import the Entity Beans
- Grails comes pre-packaged with HSQLDB, but since we're using MySQL, we have a few quick steps to tell Grails how to talk to our database. First, download the Java MySQL driver from http://www.mysql.com/products/connector/j/. I opted for the current production-ready version which, as of this writing, is 3.1.13.
- Open the zip file and extract the mysql-connector-java-3.1.13-bin.jar file into the lib directory of your Grails application - in our case, that's ejb3_grails/lib. (Please note that the exact name of the JAR file may differ based on the version of the driver you downloaded.)
- Now we're ready to tell Grails where to find our database. Open ApplicationDataSource.groovy in your editor of choice, and modify it to match the settings below. You'll find this file in ejb3_grails/grails-app/conf/. (Note that you'll need to change the username and password to the appropriate values for your MySQL account.)
import org.codehaus.groovy.grails.orm.hibernate.cfg.GrailsAnnotationConfiguration class ApplicationDataSource { def configClass = GrailsAnnotationConfiguration.class boolean pooling = true //String dbCreate = "create-drop" // one of 'create', 'create-drop','update' String url = "jdbc:mysql://localhost/ejb3example" String driverClassName = "com.mysql.jdbc.Driver" String username = "ejb3example" String password = "ejb3example" }
In addition to specifying the connectivity settings, we also need to define the configClass member to allow Grails to support the annotations used in our entity beans.
Lastly, we want to comment out the dbCreate setting. This setting allows you to have Grails update your database schema at runtime to synchronize it with your domain classes. While this is a powerful option, we simply don't need it for this example. By commenting out this property, we're instructing Grails to leave the schema as is.
-
Next, we need to copy the entity beans - EmployeeBean and ComputerBean - into our Grails project. Grails looks for Java classes in the src/java directory. Be sure to create the full directory structure matching the package name for these classes.
jMac:~/dev/ejb3_grails/src/java/com/jasonrudolph/ejb3example/entity jason$ ls ComputerBean.java EmployeeBean.java
Long-term, you'll want to make sure that these files stay in sync with the official copy of these classes (in the source tree of your JEE project). To do so, you could easily instruct your build script to copy these files from the JEE project into the Grails project at build time.
- Grails allows us to work with any Java classes, but we want Grails to give special treatment to these particular Java classes (i.e., our entity beans). We want Grails to recognize these classes as our domain classes and to provide all the ORM and dynamic method goodness that comes with a Grails domain class. To do so, we'll register these classes by adding the following hibernate.cfg.xml file to the hibernate directory of our application.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <mapping package="com.jasonrudolph.ejb3example.entity" /> <mapping class="com.jasonrudolph.ejb3example.entity.EmployeeBean" /> <mapping class="com.jasonrudolph.ejb3example.entity.ComputerBean" /> </session-factory> </hibernate-configuration>
Step 4 - Generate the Scaffolding
Now we're ready for the real time savings to begin. There is just no escaping most of the things we've done so far. (No matter how smart your framework is, you'll always have to tell it where to find your database.) However, building a good and functional starting point for your user interface is no longer a manual task.
- Make sure you're in the project's root directory - in our case, it's ejb3_grails. Then, enter grails generate-controller. When asked for the domain class name, enter the fully-qualified class name for our first entity bean - com.jasonrudolph.ejb3example.entity.EmployeeBean.
jMac:~/dev/ejb3_grails jason$ grails generate-controller ... input-domain-class: [input] Enter domain class name: com.jasonrudolph.ejb3example.entity.EmployeeBean ... [java] Generating controller for domain class [com.jasonrudolph.ejb3example.entity.EmployeeBean] [java] Controller generated at ./grails- app/controllers/EmployeeBeanController.groovy BUILD SUCCESSFUL Total time: 10 seconds
- Now that we have the controller, let's generate the corresponding views. Enter grails generate-views. When asked for the domain class name, enter com.jasonrudolph.ejb3example.entity.EmployeeBean.
jMac:~/dev/ejb3_grails jason$ grails generate-views ... input-domain-class: [input] Enter domain class name: com.jasonrudolph.ejb3example.entity.EmployeeBean ... [java] Generating views for domain class [com.jasonrudolph.ejb3example.entity.EmployeeBean] [java] Generating list view for domain class [com.jasonrudolph.ejb3example.entity.EmployeeBean] [java] list view generated at /Users/jason/dev/ejb3_grails/./grails- app/views/employeeBean/list.gsp [java] Generating show view for domain class [com.jasonrudolph.ejb3example.entity.EmployeeBean] [java] Show view generated at /Users/jason/dev/ejb3_grails/./grails- app/views/employeeBean/show.gsp [java] Generating edit view for domain class [com.jasonrudolph.ejb3example.entity.EmployeeBean] [java] Edit view generated at /Users/jason/dev/ejb3_grails/./grails- app/views/employeeBean/edit.gsp [java] Generating create view for domain class [com.jasonrudolph.ejb3example.entity.EmployeeBean] [java] Create view generated at /Users/jason/dev/ejb3_grails/./grails- app/views/employeeBean/create.gsp BUILD SUCCESSFUL Total time: 11 seconds
-
Repeat this process to generate the controller and views for the other entity bean (i.e., com.jasonrudolph.ejb3example.entity.ComputerBean).
-
Now let's run our application and see just how much we can get for such little effort. Enter grails run-app, and we're ready to go. Open your browser to http://localhost:8080/ejb3_grails/.
-
OK. We have our two controllers. And, as is suggested by the text here, we'll eventually want to replace this page with our own custom landing page. For now, let's move on to the EmployeeBeanController.
We see most of the information we'd want here. Obviously the Computers column needs some work, but otherwise this page can probably be ready to go with just a few cosmetic changes.
Take some time to look around the application and see what we have so far. Try to create a new employee, edit an employee, and then (if you're feeling particularly powerful) terminate one of those poor souls. Try out similar features for managing computers. Along the way, make note of the things you'd want to change as well as the things we need to change. At the same time, be sure to consider those things that already meet your needs. Those are the features you got for free!
...
Done? OK. Here's my take on the list of things we need, and then we can talk about those nice-to-have items later.
- Relationship Management - The scaffolding clearly tries in this area, but it just doesn't meet our needs. Luckily, this is pretty easy to remedy. (Heck. Even the Ruby on Rails scaffolding doesn't give you relationship management for free!)
- Validation - It's just missing. Plain and simple. But, we can quickly add that as well.
That's it. If we can implement proper relationship management and validation, we will have a fully-functioning web app to manage our entity beans. After that, the rest is just gravy. (Though we'll be sure to sample some of that tasty gravy before we're finished.)
Step 5 - Add Relationship Management
What do we expect from the application in regards to relationship management? Well, I'd say we should reasonably be able to ...
- View all computers assigned to an employee.
- View the details for a single computer (including its assignment status).
- Create, update, and delete a computer (including its assignment status).
Ready? Let's get started.
-
It doesn't really make sense to list computers on the Employees page, so let's remove that column. Open grails-app/views/employeeBean/list.gsp, and remove that column. Now just refresh the browser, and verify the updated page.
-
Next, click the Show link to view the details for an employee.
At the very least, we need to clean up the text listed for each computer. But perhaps we don't want to see the computers directly on this page at all. Instead of showing the computers on this page, let's include a link to this employee's list of computers.
Open the template for this page (i.e., grails-app/views/employeeBean/show.gsp), and remove the row that currently displays the employee's computers. Then, add the following row to link to a separate page showing the computers for this employee.
<tr class="prop"> <td colspan="2" align="left" class="name"> <g:link controller='computerBean' action='showComputersByEmployee' id='${employeeBean.id}' >Show Computers</g:link> </td> </tr>
We're using the Grails tag library to help us out here. The link tag will generate a link to the ComputerBeanController and invoke a new action that we need to define called showComputersByEmployee. That link will also include the ID for the employee in question.
Let's refresh the browser and see our changes.
OK. We have our link. Now we need to define the new action for that link. Open grails-app/controllers/ComputerBeanController.groovy in your editor. Because the new action will search for computers by employee, we first need to add an import statement for the EmployeeBean class.
import com.jasonrudolph.ejb3example.entity.EmployeeBean
Then we just add the new action.
def showComputersByEmployee = { render(view:'list', model:[ computerBeanList: ComputerBean.findAllByEmployeeBean(EmployeeBean.get(params.id)) ]) }
This action gives us a good look at just how much you can say in Groovy (and Grails) in a few concise lines. In those few lines, we're telling Grails that any calls to showComputersByEmployee should ...
- Get the employee ID from the request using params.id
- Get the EmployeeBean for that employee ID using the EmployeeBean#get method
- Find all computers assigned to that employee using the ComputerBean#findAllByEmployeeBean method
- Put the results in an object named computerBeanList
- render the view, using the computerBeanList object as the model, in a template named list
Remember defining the get method in EmployeeBean and the findAllByEmployeeBean method in ComputerBean? No? You're right. Those methods are just a small sampling of the many dynamic methods that Grails provides for your domain classes. We'll explore these items more later. In the meantime, we're ready to click on the Show Computers link.
We're getting close. We still need to change the text in the Employee Bean column. We certainly want something with a bit more human meaning here.
Also, we might prefer to identify the employee to which these computers belong. We're currently reusing the list template (generated as part of the scaffolding for the ComputerBean), and that template is designed primarily to list all computers. However, we could always define a separate template for displaying this subset of computers or perhaps make the list template more dynamic in order to support both scenarios. For now, we'll tuck this feature away as nice-to-have but not essential.
That should complete all the changes we need for managing employees. Now we just need to clean up the computer management features.
- Since we're here, let's clean up the computer list template. Instead of the current text in the Employee Bean column, let's change it to display the employee's network ID. Open grails-app/views/computerBean/list.gsp. Find the Groovy scriptlet that renders the text in the Employee Bean column ...
${it.employeeBean}
... and change it to render the employee's network ID.
${it.employeeBean.networkId}
Refresh the browser and let's see how it looks. (The Employee column seemed a little awkward between the Brand and the Model columns, so I switched things around a bit. Feel free to do the same.)
The list template is complete. On the show template.
-
Click the Show link, and let's assess what we need to change.
It looks like we just need to change the text for the employee link. We're almost pros at this by now. So, open grails-app/views/computerBean/show.gsp, and find the scriptlet that renders the current text for the link.
${computerBean?.employeeBean}
Just like we did for the list template, change the code to display the employee's network ID.
${computerBean?.employeeBean.networkId}
Let's refresh the browser again and verify our changes. (Again, the Employee row seemed a little awkward between the Brand and the Model rows, so you're welcome to rearrange the rows as you see fit.)
-
Let's move on the edit functionality.
We're starting to see a theme here. We need to change the select box to provide a list of network IDs. Find the <g:select> tag in grails-app/views/computerBean/edit.gsp, and tweak it like so.
<g:select optionKey="id" from="${com.jasonrudolph.ejb3example.entity.EmployeeBean.list()}" name='employeeId' optionValue='networkId' value='${computerBean.employeeBean?.id}'> </g:select>
By adding the optionValue parameter to the tag, the text in the select box has taken on a more meaningful form.
The view is correct now, but the update functionality requires a bit of effort outside of the view as well. We also need to enhance the controller (i.e., ComputerBeanController.groovy). If the user changes the employee associated with a computer, we need to make sure we persist those association changes properly. In other words, we need to disassociate the computer from the current employee and assign it to the new employee. The enhanced update method requires just a few additional lines of code.
def update = { def computerBean = ComputerBean.get( params.id ) if(computerBean) { if (computerBean.employeeBean) { computerBean.employeeBean.computers.remove(computerBean) } computerBean.properties = params def employeeBean = EmployeeBean.get(params.employeeId) employeeBean.computers.add(computerBean) computerBean.employeeBean = employeeBean if(computerBean.save()) { redirect(action:show,id:computerBean.id) } else { render(view:'edit',model:[computerBean:computerBean]) } } else { flash.message = "ComputerBean not found with id ${params.id}" redirect(action:edit,id:params.id) } }
Save your changes, and we're ready to try it out. Let's change the brand for this computer - it's a Lenovo now - and let's reassign it to John Doe.
-
Of course, we also need to be able to add new computers to our inventory. So, let's click on New ComputerBean.
After our last change to the Edit page, we're well-qualified to clean up this page. We need to make the same adjustments to the select box. Open grails-app/views/computerBean/create.gsp, adjust the <g:select> tag, and refresh your browser.
<g:select optionKey="id" from="${com.jasonrudolph.ejb3example.entity.EmployeeBean.list()}" name='employeeId' optionValue='networkId' value='${computerBean.employeeBean?.id}'> </g:select>
As we saw for the edit functionality, we need to make a slight enhancement to the controller. When we create a new computer, we need to make sure we assign it to an employee before we save it. Edit ComputerBeanController.groovy to include this updated save method.
def save = { def computerBean = new ComputerBean() computerBean.properties = params def employeeBean = EmployeeBean.get(params.employeeId) employeeBean.computers.add(computerBean) computerBean.employeeBean = employeeBean if(computerBean.save()) { redirect(action:show,id:computerBean.id) } else { render(view:'create',model:[computerBean:computerBean]) } }
Back in the browser, we're ready to create a new computer. Fill in the empty fields and create away.
Jane now has a hot new laptop.
-
And the last piece we need is the delete functionality. We don't need any template changes this time. We just need to add one line to the controller. When we delete a computer in ComputerBeanController.groovy, we also need to remove the computer's association to the employee. The fourth line below will take care of that.
def delete = { def computerBean = ComputerBean.get( params.id ) if(computerBean) { computerBean.employeeBean.getComputers().remove(computerBean) computerBean.delete() flash.message = "ComputerBean ${params.id} deleted." redirect(action:list) } else { flash.message = "ComputerBean not found with id ${params.id}" redirect(action:list) } }
It looks like Jane's new MacBook has been recalled. Shall we delete it?
And there we have it! In just a few quick steps, we now have a functioning web application built on top of our entity beans. It can still use a little polish, of course. At the very least, it's completely sufficient as a working prototype. Better yet, it's a prototype that is fully capable of growing into the finished product.
Step 6 - Define Validation Rules
One thing we certainly need in the near term is some validation logic. Fortunately, Grails offers a very convenient declarative validation mechanism. For each of our entity beans, we simply need to add a Groovy script to define the appropriate constraints. By convention, Grails looks for a script with the same name as your domain class but ending with the word "Constraints" (e.g., EmployeeBeanConstraints.groovy).
So, let's define the constraints for each of our entity beans. At a minimum we need our constraints to match our data model. We'll also add in some basic business validation. For example, the database requires that network IDs not exceed eight characters in length, but we'll also enforce our business rule that a network ID must be no fewer than six characters in length.
Grails looks for the constraints declarations in the same directory as our domain class. For the EmployeeBean class, we need to create a file named EmployeeBeanConstraints.groovy and place it in ejb3_grails/src/java/com/jasonrudolph/ejb3example/entity/. The file should include the following rules:
constraints = { networkId(length:6..8,blank:false,unique:true) firstName(maxLength:20,blank:false) lastName(maxLength:20,blank:false) startDate(nullable:false) }
Next, we'll define the constraints for the ComputerBean class. The file goes in the same directory, and we need to name it ComputerBeanConstraints.groovy.
constraints = { serialNumber(maxLength:20,blank:false) brand(maxLength:20,blank:false) model(maxLength:20,blank:false) }
To make these constraints take effect, we need to restart the application. In the console where you started the application, enter Control-C to stop the application. Then, enter grails run-app, and we're ready to test the validation.
Now, if we try to sneak in a new employee with an invalid network ID, our application knows not to accept it.
Naturally we'll want change the error message to something more business-focused and less technical. Grails provides a message resources file for you to define your own custom error message.
The constraints we defined represent just a small sample of the validation that Grails offers. Be sure to check out the Grails documentation for the complete list of available constraints.
Step 7 - Get Dynamic
Everything up to this point has shown us that we can build web applications more quickly than we might have thought, and we've seen some impressive features of this up-and-coming framework. That said, here's where the real magic begins. Recall our brief mention of dynamic methods back when we added the ability to see all computers assigned to a user? Let's take a deeper at look at this feature.
Once we have some basic CRUD functionality like this, one of the first requests we get is, "I need a way to see all employees by _______." Or, "Show me all computers made by _______ with serial numbers starting with _______." These are reasonable enough requests, but how much effort typically goes into building these simple reports? Too often it requires a new method in some session bean, a new mapping in one of my web framework's configuration files, perhaps even some SQL, etc. Not only can we get by without those things, we can do so and be ready to quickly adapt to the next change.
For starters, let's say we need to be able to find all employees by last name. We'll add a new menu option to the Employee List page to take us to our new query page. Add this block to the menu section of grails-app/views/employeeBean/list.gsp.
<span class="menuButton"> <g:link action="search">Search Employees</g:link> </span>
Clicking this menu option will invoke a new action in our controller named search. We don't need that action to do anything more than render our search input page. So, add an empty method in EmployeeBeanController.groovy to accept these requests.
def search = { }
This empty action tells Grails that search is a valid action for this controller. When Grails receives a request for this action, it will simply look for a template with the same name (i.e., search.gsp) in grails-app/views/employeeBean/ and render its content.
Since we really just want a simple form with input fields for an employee, we can use the create.gsp template as a good staring point. When we're finished, our new search.gsp template should look like this:
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta name="layout" content="main" /> <title>Search Employees</title> </head> <body> <div class="nav"> <span class="menuButton"> <a href="${createLinkTo(dir:'')}">Home</a></span> <span class="menuButton"> <g:link action="list">EmployeeBean List</g:link></span> </div> <div class="body"> <h1>Search Employees</h1> <g:form action="showSearchResults" method="post" > <div class="dialog"> <table> <tr class='prop'> <td valign='top' class='name'> <label for='lastName'>Last Name Like:</label> </td> <td valign='top' class='value'> <input type='text' name='lastName' value='' /> </td> </tr> </table> </div> <div class="buttons"> <span class="formButton"> <input type="submit" value="Search"></input> </span> </div> </g:form> </div> </body> </html>
If you're thinking that we shouldn't be repeating so much of the wrapper content (e.g., the <head> tag, etc.), you're right. Grails offers various layout mechanisms to avoid this duplication. (We'll consider that an exercise for the reader.) In the meantime, our search page awaits. (While Grails generally provides dynamic deployment of such changes, some readers have reported the need to restart the application in order to successfully access the new search page. To do so, enter Control-C in the console where you started the application. Then, enter grails run-app, and make your way to the search page.)
You'll notice that in search.gsp we specified that the form should post all requests to a new action named (plainly enough) showSearchResults. So, we need to define that new action in EmployeeBeanController.groovy.
def showSearchResults = { render(view:'list', model:[ employeeBeanList: EmployeeBean.findAllByLastNameLike("%" + params.lastName + "%") ]) }
Because we just want to display a list of matching employees, we can safely use the existing list.gsp template. This is another example of Grails's ability to provide significant functionality in a few succinct commands. All requests to the showSearchResults action will ...
- Get the search criteria from the request using params.lastName
- Get a list of EmployeeBean objects for employees matching that last name
- Put the results in an object named employeeBeanList
- render the view, using the employeeBeanList object as the model, in the list template
When we click Search, sure enough, we get the matching results.
Of course, if we can search employees by last name, why not search by first name as well? We can easily add a new input field to our search page.
<tr class='prop'> <td valign='top' class='name'> <label for='lastName'>First Name Like:</label> </td> <td valign='top' class='value'> <input type='text' name='firstName' value='' /> </td> </tr>
That's trivial enough, but what do we have to change in our controller? Very little. Instead of using the findAllByLastNameLike method, we'll now invoke findAllByLastNameLikeAndFirstNameLike.
def showSearchResults = { render(view:'list', model:[ employeeBeanList: EmployeeBean.findAllByLastNameLikeAndFirstNameLike("%" + params.lastName + "%", "%" + params.firstName + "%") ]) }
Grails automatically provides dynamic finders for just about every query combination you can think of! Try it out. Our search from above yields just what we'd expect given our updated query.
Step 8 - Build Your Own Criteria
Over time, you may find that your requirements grow in complexity. How can we handle queries that need to group various sets of criteria? For example, suppose I want to find all employees where...
- The network ID partially matches some value with case-sensitivity, or
- The first name and the last name exactly match their respective criteria.
That kind of query is beyond the capabilities of the dynamic finders that we just saw. No worries. That doesn't mean that your application has to take on any additional complexity. Grails offers the immensely flexible Hibernate Criteria Builder to satisfy that need without sacrificing any of the functionality we've seen so far.
First, let's modify our search page to capture the input for our new requirements. If we edit search.gsp and replace the current input fields with the following fields, it should give us what we're looking for.
<tr class='prop'> <td valign='top' class='name'><label for='lastName'>Network ID Like:</label></td> <td valign='top' class='value'> <input type='text' name='networkId' value='' /> </td> </tr> <tr><td>-- or --</td></tr> <tr class='prop'> <td valign='top' class='name'><label for='lastName'>First Name Equals:</label></td> <td valign='top' class='value'> <input type='text' name='firstName' value='' /></td> </tr> <tr class='prop'> <td valign='top' class='name'><label for='lastName'>Last Name Equals:</label></td> <td valign='top' class='value'> <input type='text' name='lastName' value='' /> </td> </tr>
Now let's look at the actual query logic. The following code satisfies our (admittedly odd) requirements in just a few short lines. (This code should replace our current showSearchResults method in EmployeeBeanController.groovy.)
def showSearchResults = { def criteria = EmployeeBean.createCriteria() def results = criteria { or { ilike("networkId", "%" + params.networkId + "%") and { eq("firstName", params.firstName) eq("lastName", params.lastName) } } } render(view:'list', model:[ employeeBeanList: results.adaptee ]) }
Even if you've never used Hibernate or Grails before, you can quickly get the gist of what's going on here. In a Groovy builder, each component is referred to as a "node." The or node tells us that we'll match any rows that satisfy one or more of the criteria specified by its child nodes. In this case, it has two child nodes.
The first child node returns results that satisfy a case-sensitive like operation on the networkId attribute.
ilike("networkId", "%" + params.networkId + "%")
The second child node is an and node. It returns results that match all of its child nodes. Its child nodes look for rows that exactly match (eq) the firstName and the lastName attributes.
and { eq("firstName", params.firstName) eq("lastName", params.lastName) }
Together, these nodes fulfill our query requirements.
If the grouping operators (and, or, etc.) seem oddly positioned at first, it's because the Hibernate Criteria Builder somewhat resembles a Polish notation syntax, where you specify the operator before the operands. That's certainly a departure from Java's use of relational operators, but it should become relatively intuitive after your first few queries.
Now let's see it in action.
Here we're looking for all employees where the network ID contains "jr" or the employee's name is "John Doe".
And when we click Search, we get exactly that.
Summary
How does this development process compare to the approach you use today? With each feature we added along the way, how many components would you touch to make that same change in your existing framework? XML files? DAOs? Form/view classes? Others? And what do you gain by having those additional components to maintain? Ultimate flexibility? If so, do you really need it? Some shops definitely do require that flexibility, but for the other 80%, aren't some sensible defaults (al a convention over configuration) well worth the productivity gains?
We've written very little code in this exercise, and yet we have a functioning and flexible application. We didn't change a single line in our existing entity beans. And because we've written such little code to get to this point, we have less code to maintain. We'll be writing less code tomorrow when the requirements change again. Just think how quickly you can respond to evolving business needs when your application is this agile!
Resources
- Get the source code used in this article.
- Download a sample JEE application that uses the entity beans on which we based the Grails application.
- Learn how to use other Grails features in the Grails User Guide.
- Check the Grails User Mailing List Archive for troubleshooting tips as you build your Grails applications.
- Refer to the Grails API or the Grails source code when you really want to know exactly what's going on behind the scenes.
- Check out the Groovy Home Page for information on all things Groovy.
- If you want to get a feel for building your first EJB3 application from scratch, check out Informit's "Enterprise Java Beans (EJB) 3.0" (Informit, July 2006).
About the Author
Jason Rudolph is an Application Architect at Railinc, where he develops software that helps keep trains moving efficiently throughout North America. He recently delivered an industry-wide inspection reporting and management system relied on for operational safety by Fortune 500 railroads, equipment leasing companies, and the Federal Railroad Administration. Jason's interests include dynamic languages, lightweight development methodologies, improving developer productivity, and a quest to keep programming fun. Jason holds a degree in Computer Science from the University of Virginia. He currently lives in Raleigh, NC with his wife (who can take a functional web app and make it actually look good) and his dog (who can outrun him, but is no match for squirrels). You can find Jason online at http://jasonrudolph.com.